package cloudspace.ui.applet;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import student.web.SessionPersistentMap;
import student.web.internal.PersistentStorageManager;
import student.web.internal.Snapshot;
import student.web.internal.LocalityService;

import cloudspace.ui.applet.io.CommandHandler;
import cloudspace.vm.VM;

public class AppletDistributedMap<K,V> extends AbstractMap implements Map
{

    public int size()
    {
        Map<String,Object> sessionMap = this.getInternalSessionMap();
        return sessionMap.size();
    }

    public boolean isEmpty()
    {
        Map<String,Object> sessionMap = this.getInternalSessionMap();
        return sessionMap.isEmpty();
    }

    public boolean containsKey( Object key )
    {
        Map<String,Object> sessionMap = this.getInternalSessionMap();
        return sessionMap.containsKey( key );
    }

    public boolean containsValue( Object value )
    {
        Map<String,Object> sessionMap = this.getInternalSessionMap();
        return sessionMap.containsValue( value );
    }

    public Object get( Object key )
    {
        Map<String,Object> sessionMap = this.getInternalSessionMap();
        return sessionMap.get( key );
    }
    private static final String PUT_JS_FUNC = "putSessionMap";

    private static final String PUT_EVENT = "onPutSessionMap";

    private static final String[] PUT_PARAM = new String[] { "key", "data","length"};

    private static String PUT_FUNC = JSFactory.generateJSFunction( PUT_JS_FUNC,
        PUT_EVENT,
        PUT_PARAM );
    private static class PutHandler implements CommandHandler
    {

        public CloudToApplet handleRequest(
            Map<String, Object> params,
            Map<String, Object> availableObjects ) throws Exception
        {
            String key = (String)params.get( "key" );
            String data = (String)params.get( "data" );
            int length = Integer.parseInt( (String )params.get( "length" ));
            byte[] rawData = new byte[length];
            BASE64.decodeContent( data, rawData, 0, length );
            PersistentStorageManager psm = PersistentStorageManager.getInstance();
            Object sentObject = psm.readObjectFromXML( new ByteArrayInputStream(rawData), VM.currentVM().getLoader(), new Snapshot() );
            SessionPersistentMap<Object> sMap = new SessionPersistentMap<Object>(Object.class);
            Object old = sMap.put( key, sentObject );
            StringWriter writer = new StringWriter();
            psm.writeObjectToXML( old, writer, VM.currentVM().getLoader(), new Snapshot(), new Snapshot() );
            String encoded = BASE64.encodeContent( writer.getBuffer().toString().getBytes() );
            return new CloudToApplet(encoded, writer.getBuffer().toString().getBytes().length);
        }
        
    }
    public Object put( Object key, Object value )
    {
        PersistentStorageManager psm = PersistentStorageManager.getInstance();
        StringWriter writer = new StringWriter();
        psm.writeObjectToXML( value, writer, this.getClass().getClassLoader(), new Snapshot(), new Snapshot() );
        String xmlObject = new String(writer.getBuffer().toString().getBytes());
        String encoded = BASE64.encodeContent( xmlObject.getBytes() );
        CloudResult result = AppletVM.callJScriptFunc( PUT_JS_FUNC,key,encoded, xmlObject.getBytes().length);
        String encodedResult = result.getResult( 0 );
        int length = Integer.parseInt( result.getResult( 1 ) );
        byte[] rawResult = new byte[length];
        BASE64.decodeContent( encodedResult, rawResult, 0, length );
        Object responseObject = psm.readObjectFromXML( new ByteArrayInputStream(rawResult), this.getClass().getClassLoader(), new Snapshot() );
        return responseObject;
    }
    private static final String REMOVE_JS_FUNC = "removeSessionMap";

    private static final String REMOVE_EVENT = "onRemoveSessionMap";

    private static final String[] REMOVE_PARAM = new String[] {"key"};

    private static String REMOVE_FUNC = JSFactory.generateJSFunction( REMOVE_JS_FUNC,
        REMOVE_EVENT,
        REMOVE_PARAM );
    private static class RemoveHandler implements CommandHandler
    {

        public CloudToApplet handleRequest(
            Map<String, Object> params,
            Map<String, Object> availableObjects ) throws Exception
        {
            PersistentStorageManager psm = PersistentStorageManager.getInstance();
            SessionPersistentMap<Object> sMap = new SessionPersistentMap<Object>(Object.class);
            String key = (String)params.get( "key" );
            Object removed = sMap.remove( "key" );
            StringWriter writer = new StringWriter();
            psm.writeObjectToXML( removed, writer, VM.currentVM().getLoader(), new Snapshot(), new Snapshot() );
            String encoded = BASE64.encodeContent( writer.getBuffer().toString().getBytes() );
            return new CloudToApplet(encoded, writer.getBuffer().toString().getBytes().length);
        }
        
    }
    public Object remove( Object key )
    {
        PersistentStorageManager psm = PersistentStorageManager.getInstance();
        CloudResult result = AppletVM.callJScriptFunc( REMOVE_JS_FUNC,key);
        String encodedResult = result.getResult( 0 );
        int length = Integer.parseInt( result.getResult( 1 ) );
        byte[] rawResult = new byte[length];
        BASE64.decodeContent( encodedResult, rawResult, 0, length );
        Object responseObject = psm.readObjectFromXML( new ByteArrayInputStream(rawResult), this.getClass().getClassLoader(), new Snapshot() );
        return responseObject;
    }

    public void putAll( Map t )
    {
        for(Entry<String,Object> entry : (Set<java.util.Map.Entry<String, Object>>)t.entrySet())
            put(entry.getKey(),entry.getValue());
        
    }
    private static final String CLEAR_JS_FUNC = "clearSessionMap";

    private static final String CLEAR_EVENT = "onClearSessionMap";

    private static final String[] CLEAR_PARAM = new String[] { };

    private static String CLEAR_FUNC = JSFactory.generateJSFunction( CLEAR_JS_FUNC,
        CLEAR_EVENT,
        CLEAR_PARAM );
    private static class ClearHandler implements CommandHandler
    {

        public CloudToApplet handleRequest(
            Map<String, Object> params,
            Map<String, Object> availableObjects ) throws Exception
        {
            SessionPersistentMap<Object> sMap = new SessionPersistentMap<Object>(Object.class);
            sMap.clear();
            return new CloudToApplet();
        }
        
    }
    public void clear()
    {
        CloudResult result = AppletVM.callJScriptFunc( CLEAR_JS_FUNC);
        if(result.isError())
        {
            throw new RuntimeException(result.getException());
        }
    }

    public Set keySet()
    {
        Map<String,Object> sessionMap = this.getInternalSessionMap();
        return sessionMap.keySet();
    }

    public Collection values()
    {
        Map<String,Object> sessionMap = this.getInternalSessionMap();
        return sessionMap.values();
    }
    private static final String ENTRY_JS_FUNC = "getEntrySetSessionMap";

    private static final String ENTRY_EVENT = "onGetEntrySetSessionMap";

    private static final String[] ENTRY_PARAM = new String[] { };

    private static String ENTRY_FUNC = JSFactory.generateJSFunction( ENTRY_JS_FUNC,
        ENTRY_EVENT,
        ENTRY_PARAM );
    private static class FullMapHandler implements CommandHandler
    {

        public CloudToApplet handleRequest(
            Map<String, Object> params,
            Map<String, Object> availableObjects ) throws Exception
        {
            SessionPersistentMap<Object> shared = new SessionPersistentMap<Object>(Object.class);
            PersistentStorageManager psm = PersistentStorageManager.getInstance();
            StringWriter out = new StringWriter();
            Map sessionMap = LocalityService.getSupportStrategy().getSessionPersistentMap();
            psm.writeObjectToXML(sessionMap, out, VM.currentVM().getLoader(), new Snapshot(), new Snapshot() );
            String output = out.getBuffer().toString();
            String result = BASE64.encodeContent( output.getBytes() );
            return new CloudToApplet(result, output.getBytes().length);
        }
        
    }
    private Map<String, Object> getInternalSessionMap()
    {
        CloudResult result = AppletVM.callJScriptFunc( ENTRY_JS_FUNC);
        String output = (String)result.getResult( 0 );
        int length = Integer.parseInt(result.getResult( 1 ));
        byte[] decodedBytes = new byte[length];
        BASE64.decodeContent( output, decodedBytes, 0,decodedBytes.length );
        String decoded = new String(decodedBytes);
        PersistentStorageManager psm = PersistentStorageManager.getInstance();
        ByteArrayInputStream in = new ByteArrayInputStream(decoded.getBytes());
        Map map = (Map)psm.readObjectFromXML( in, this.getClass().getClassLoader(), new Snapshot() );
        return map;
    }
    public Set entrySet()
    {
        Map<String,Object> sMap = getInternalSessionMap();
        
        return sMap.entrySet();
    }
    
    private static final List<String> localScripts = new ArrayList<String>();
    static
    {
        localScripts.add( ENTRY_FUNC );
        localScripts.add( CLEAR_FUNC );
        localScripts.add( PUT_FUNC );
        localScripts.add( REMOVE_FUNC );
    }

    public static List<String> getJavaScript()
    {
        return localScripts;
    }


    public static Map<String, CommandHandler> getCommandHandlers()
    {
        final Map<String, CommandHandler> localEvents = new HashMap<String, CommandHandler>();
        localEvents.put( ENTRY_EVENT, new FullMapHandler() );
        localEvents.put( CLEAR_EVENT, new ClearHandler());
        localEvents.put( PUT_EVENT, new PutHandler() );
        localEvents.put( REMOVE_EVENT, new RemoveHandler() );
        return localEvents;
    }

}
